#pragma once


namespace evm {

/* Every operator class template, has an additional, but optional, template type parameter B.
 * This parameter will be a publicly-derived base class of the instantiated template.
 * This means it must be a class type. It can be used to avoid the bloating of object sizes
 * that is commonly associated with multiple-inheritance from several empty base classes.
 * To provide support for a group of operators, use the B parameter to chain operator templates
 * into a single-base class hierarchy. The technique is also used by the composite operator
 * templates to group operator definitions. If a chain becomes too long for the compiler to support,
 * try replacing some of the operator templates with a single grouped operator template that
 * chains the old templates together; the length limit only applies to the number of templates
 * directly in the chain, not those hidden in group templates.
 */
namespace detail {

template<typename T> class empty_base {};

}

// T should define >, <
template <class T, class U, class B = detail::empty_base<T>>
struct less_than_comparable2 : B
{
    friend bool operator<=(const T& x, const U& y) { return !static_cast<bool>(x > y); }
    friend bool operator>=(const T& x, const U& y) { return !static_cast<bool>(x < y); }
    friend bool operator>(const U& x, const T& y)  { return y < x; }
    friend bool operator<(const U& x, const T& y)  { return y > x; }
    friend bool operator<=(const U& x, const T& y) { return !static_cast<bool>(y < x); }
    friend bool operator>=(const U& x, const T& y) { return !static_cast<bool>(y > x); }
};

// T should define <
template <class T, class B = detail::empty_base<T>>
struct less_than_comparable1 : B
{
    friend bool operator>(const T& x, const T& y)  { return y < x; }
    friend bool operator<=(const T& x, const T& y) { return !static_cast<bool>(y < x); }
    friend bool operator>=(const T& x, const T& y) { return !static_cast<bool>(x < y); }
};

// T should define ==
template <class T, class U, class B = detail::empty_base<T>>
struct equality_comparable2 : B
{
    friend bool operator==(const U& y, const T& x) { return x == y; }
    friend bool operator!=(const U& y, const T& x) { return !static_cast<bool>(x == y); }
    friend bool operator!=(const T& y, const U& x) { return !static_cast<bool>(y == x); }
};

// T should define ==
template <class T, class B = detail::empty_base<T>>
struct equality_comparable1 : B
{
    friend bool operator!=(const T& x, const T& y) { return !static_cast<bool>(x == y); }
};

// ============ "operator +=" -> "operator +" ============

// T should define +
template <class T, class U, class B = detail::empty_base<T>>
struct addable2 : B
{
    friend T operator+(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv += rhs;
        return nrv;
    }
    friend T operator+(const U& lhs, const T& rhs) {
        T nrv(rhs);
        nrv += lhs;
        return nrv;
    }
};

// T should define +
template <class T, class B = detail::empty_base<T>>
struct addable1 : B
{
    friend T operator+(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv += rhs;
        return nrv;
    }
};

// ============ "operator -=" -> "operator -" ============

// T should define -
template <class T, class B = detail::empty_base<T>>
struct subtractable2 : B
{
    friend T operator-(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv -= rhs;
        return nrv;
    }
};

// U should define -
template <class T, class U, class B = detail::empty_base<T>>
struct subtractable2_left : B
{
    friend T operator-(const U& lhs, const T& rhs) {
        T nrv(lhs);
        nrv -= rhs;
        return nrv;
    }
};

// T should define -
template <class T, class B = detail::empty_base<T>>
struct subtractable1 : B
{
    friend T operator-(const T& lhs, const T& rhs) {
        T nrv(lhs);
        nrv -= rhs;
        return nrv;
    }
};

// ============ "operator *=" -> "operator *" ============

// T should define *
template <class T, class U, class B = detail::empty_base<T>>
struct multipliable2 : B
{
    friend T operator*(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv *= rhs;
        return nrv;
    }
    friend T operator*(const U& lhs, const T& rhs) {
        T nrv(rhs);
        nrv *= lhs;
        return nrv;
    }
};

// T should define *
template <class T, class B = detail::empty_base<T>>
struct multipliable1 : B
{
    friend T operator*(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv *= rhs;
        return nrv;
    }
};

// ============ "operator /=" -> "operator /" ============

// T should define /
template <class T, class B = detail::empty_base<T>>
struct dividable2 : B
{
    friend T operator/(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv /= rhs;
        return nrv;
    }
};

// U should define /
template <class T, class U, class B = detail::empty_base<T>>
struct dividable2_left : B
{
    friend T operator/(const U& lhs, const T& rhs) {
        T nrv(lhs);
        nrv /= rhs;
        return nrv;
    }
};

// T should define /
template <class T, class B = detail::empty_base<T>>
struct dividable1 : B
{
    friend T operator/(const T& lhs, const T& rhs) {
        T nrv(lhs);
        nrv /= rhs;
        return nrv;
    }
};

// ============ "operator %=" -> "operator %" ============

// T should define %
template <class T, class B = detail::empty_base<T>>
struct modable2 : B
{
    friend T operator%(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv %= rhs;
        return nrv;
    }
};

// U should define %
template <class T, class U, class B = detail::empty_base<T>>
struct modable2_left : B
{
    friend T operator%(const U& lhs, const T& rhs) {
        T nrv(lhs);
        nrv %= rhs;
        return nrv;
    }
};

// T should define %
template <class T, class B = detail::empty_base<T>>
struct modable1 : B
{
    friend T operator%(const T& lhs, const T& rhs) {
        T nrv(lhs);
        nrv %= rhs;
        return nrv;
    }
};

// ============ "operator ^=" -> "operator ^" ============

// T should define ^
template <class T, class U, class B = detail::empty_base<T>>
struct xorable2 : B
{
    friend T operator^(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv ^= rhs;
        return nrv;
    }
    friend T operator^(const U& lhs, const T& rhs) {
        T nrv(rhs);
        nrv ^= lhs;
        return nrv;
    }
};

// T should define ^
template <class T, class B = detail::empty_base<T>>
struct xorable1 : B
{
    friend T operator^(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv ^= rhs;
        return nrv;
    }
};

// ============ "operator &=" -> "operator &" ============

// T should define &
template <class T, class U, class B = detail::empty_base<T>>
struct andable2 : B
{
    friend T operator&(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv &= rhs;
        return nrv;
    }
    friend T operator&(const U& lhs, const T& rhs) {
        T nrv(rhs);
        nrv &= lhs;
        return nrv;
    }
};

// T should define &
template <class T, class B = detail::empty_base<T>>
struct andable1 : B
{
    friend T operator&(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv &= rhs;
        return nrv;
    }
};

// ============ "operator |=" -> "operator |" ============

// T should define |
template <class T, class U, class B = detail::empty_base<T>>
struct orable2 : B
{
    friend T operator|(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv |= rhs;
        return nrv;
    }
    friend T operator|(const U& lhs, const T& rhs) {
        T nrv(rhs);
        nrv |= lhs;
        return nrv;
    }
};

// T should define |
template <class T, class B = detail::empty_base<T>>
struct orable1 : B
{
    friend T operator|(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv |= rhs;
        return nrv;
    }
};


// incrementable and decrementable
// T should define prefix ++
template <class T, class B = detail::empty_base<T>>
struct incrementable : B
{
    friend T operator++(T& x, int) {
        T nrv(x);
        ++x;
        return nrv;
    }
};

// T should define prefix --
template <class T, class B = detail::empty_base<T>>
struct decrementable : B
{
    friend T operator--(T& x, int) {
        T nrv(x);
        --x;
        return nrv;
    }
};

// Iterator operator classes

template <class T, class P, class B = detail::empty_base<T>>
struct dereferenceable : B
{
    P operator->() const
    {
        return &*static_cast<const T&>(*this);
    }
};

template <class T, class I, class R, class B = detail::empty_base<T>>
struct indexable : B
{
    R operator[](I n) const
    {
        return *(static_cast<const T&>(*this) + n);
    }
};


// T should define <<=
template <class T, class U, class B = detail::empty_base<T>>
struct left_shiftable2 : B
{
    friend T operator<<(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv <<= rhs;
        return nrv;
    }
};

// T should define <<=
template <class T, class B = detail::empty_base<T>>
struct left_shiftable1 : B
{
    friend T operator<<(const T& lhs, const T& rhs) {
        T nrv(lhs);
        nrv <<= rhs;
        return nrv;
    }
};

// T should define >>=
template <class T, class U, class B = detail::empty_base<T>>
struct right_shiftable2 : B
{
    friend T operator>>(const T& lhs, const U& rhs) {
        T nrv(lhs);
        nrv >>= rhs;
        return nrv;
    }
};

// T should define <<=
template <class T, class B = detail::empty_base<T>>
struct right_shiftable1 : B
{
    friend T operator>>(const T& lhs, const T& rhs) {
        T nrv(lhs);
        nrv >>= rhs;
        return nrv;
    }
};

// T should define >, <
template <class T, class U, class B = detail::empty_base<T>>
struct equivalent2 : B
{
    friend bool operator==(const T& x, const U& y) {
        return !static_cast<bool>(x < y) && !static_cast<bool>(x > y);
    }
};

// T should define >, <
template <class T, class B = detail::empty_base<T>>
struct equivalent1 : B
{
    friend bool operator==(const T&x, const T&y) {
        return !static_cast<bool>(x < y) && !static_cast<bool>(y < x);
    }
};

// T should define <, >, ==
template <class T, class U, class B = detail::empty_base<T>>
struct partially_ordered2 : B
{
    friend bool operator<=(const T& x, const U& y) {
        return static_cast<bool>(x < y) || static_cast<bool>(x == y);
    }

    friend bool operator>=(const T& x, const U& y) {
        return static_cast<bool>(x > y) || static_cast<bool>(x == y);
    }

    friend bool operator>(const U& x, const T& y) {
        return y < x;
    }

    friend bool operator<(const U& x, const T& y) {
        return y > x;
    }

    friend bool operator<=(const U& x, const T& y) {
        return static_cast<bool>(y > x) || static_cast<bool>(y == x);
    }

    friend bool operator>=(const U& x, const T& y) {
        return static_cast<bool>(y < x) || static_cast<bool>(y == x);
    }
};

// T should define <, ==
template <class T, class B = detail::empty_base<T>>
struct partially_ordered1 : B
{
    friend bool operator>(const T& x, const T& y) {
        return y < x;
    }

    friend bool operator<=(const T& x, const T& y) {
        return static_cast<bool>(x < y) || static_cast<bool>(x == y);
    }

    friend bool operator>=(const T& x, const T& y) {
        return static_cast<bool>(y < x) || static_cast<bool>(x == y);
    }
};

/* ========== Combined Operators ========== */

// T should define >, <, ==
template <class T, class U, class B = detail::empty_base<T>>
struct totally_ordered2 : less_than_comparable2<T, U, equality_comparable2<T, U, B>> {};

// T should define >, ==
template <class T, class B = detail::empty_base<T>>
struct totally_ordered1 : less_than_comparable1<T, equality_comparable1<T, B>> {};

// T should define +=, -=
template <class T, class U, class B = detail::empty_base<T>>
struct additive2 : addable2<T, U, subtractable2<T, U, B>> {};

// T should define +=, -=
template <class T, class B = detail::empty_base<T>>
struct additive1 : addable1<T, subtractable1<T, B>> {};

// T should define *=, /=
template <class T, class U, class B = detail::empty_base<T>>
struct multiplicative2 : multipliable2<T, U, dividable2<T, U, B>> {};

// T should define *=, /=
template <class T, class B = detail::empty_base<T>>
struct multiplicative1 : multipliable1<T, dividable1<T, B>> {};

// T should define *=, /=, %=
template <class T, class U, class B = detail::empty_base<T>>
struct integer_multiplicative2 : multiplicative2<T, U, modable2<T, U, B>> {};

// T should define *=, /=, %=
template <class T, class B = detail::empty_base<T>>
struct integer_multiplicative1 : multiplicative1<T, modable1<T, B>> {};

// T should define +=, -=, *=, /=
template <class T, class U, class B = detail::empty_base<T>>
struct arithmetic2 : additive2<T, U, multiplicative2<T, U, B>> {};

// T should define +=, -=, *=, /=
template <class T, class B = detail::empty_base<T>>
struct arithmetic1 : additive1<T, multiplicative1<T, B>> {};

// T should define +=, -=, *=, /=, %=
template <class T, class U, class B = detail::empty_base<T>>
struct integer_arithmetic2 : additive2<T, U, integer_multiplicative2<T, U, B>> {};

// T should define +=, -=, *=, /=, %=
template <class T, class B = detail::empty_base<T>>
struct integer_arithmetic1 : additive1<T, integer_multiplicative1<T, B>> {};

// T should define &=, |=, ^=
template <class T, class U, class B = detail::empty_base<T>>
struct bitwise2 : xorable2<T, U, andable2<T, U, orable2<T, U, B>>> {};

// T should define &=, |=, ^=
template <class T, class B = detail::empty_base<T>>
struct bitwise1 : xorable1<T, andable1<T, orable1<T, B>>> {};

template <class T, class B = detail::empty_base<T>>
struct unit_steppable : incrementable<T, decrementable<T, B>> {};

template <class T, class U, class B = detail::empty_base<T>>
struct shiftable2 : left_shiftable2<T, U, right_shiftable2<T, U, B>> {};

template <class T, class B = detail::empty_base<T>>
struct shiftable1 : left_shiftable1<T, right_shiftable1<T, B>> {};

template <class T, class U, class B = detail::empty_base<T>>
struct ring_operators2 : additive2<T, U, subtractable2_left<T, U, multipliable2<T, U, B>>> {};

template <class T, class B = detail::empty_base<T>>
struct ring_operators1 : additive1<T, multipliable1<T, B>> {};

template <class T, class U, class B = detail::empty_base<T>>
struct ordered_ring_operators2 : ring_operators2<T, U, totally_ordered2<T, U, B>> {};

template <class T, class B = detail::empty_base<T>>
struct ordered_ring_operators1 : ring_operators1<T, totally_ordered1<T, B>> {};

template <class T, class U, class B = detail::empty_base<T>>
struct field_operators2 : ring_operators2<T, U, dividable2<T, U, dividable2_left<T, U, B>>> {};

template <class T, class B = detail::empty_base<T>>
struct field_operators1 : ring_operators1<T, dividable1<T, B>> {};

template <class T, class U, class B = detail::empty_base<T>>
struct ordered_field_operators2 : field_operators2<T, U, totally_ordered2<T, U, B>> {};

template <class T, class B = detail::empty_base<T>>
struct ordered_field_operators1 : field_operators1<T, totally_ordered1<T, B>> {};

template <class T, class U, class B = detail::empty_base<T>>
struct euclidian_ring_operators2 : ring_operators2<T, U, dividable2<T, U, dividable2_left<T, U,
                                   modable2<T, U, modable2_left<T, U, B>>>>> {};

template <class T, class B = detail::empty_base<T>>
struct euclidian_ring_operators1 : ring_operators1<T, dividable1<T, modable1<T, B>>> {};

template <class T, class U, class B = detail::empty_base<T>>
struct ordered_euclidian_ring_operators2 : totally_ordered2<T, U, euclidian_ring_operators2<T, U, B>> {};

template <class T, class B = detail::empty_base<T>>
struct ordered_euclidian_ring_operators1 : totally_ordered1<T, euclidian_ring_operators1<T, B>> {};

template <class T, class U, class B = detail::empty_base<T>>
struct euclidean_ring_operators2 : ring_operators2<T, U, dividable2<T, U, dividable2_left<T, U,
                                   modable2<T, U, modable2_left<T, U, B>>>>> {};

template <class T, class B = detail::empty_base<T>>
struct euclidean_ring_operators1 : ring_operators1<T, dividable1<T, modable1<T, B>>> {};

template <class T, class U, class B = detail::empty_base<T>>
struct ordered_euclidean_ring_operators2 : totally_ordered2<T, U, euclidean_ring_operators2<T, U, B>> {};

template <class T, class B = detail::empty_base<T>>
struct ordered_euclidean_ring_operators1 : totally_ordered1<T, euclidean_ring_operators1<T, B>> {};

/* ========== Grouped Iterator Operators ========== */
/* There are five iterator operator class templates, each for a different category of iterator. */

template <class T, class P, class B = detail::empty_base<T>>
struct input_iteratable : equality_comparable1<T, incrementable<T, dereferenceable<T, P, B>>> {};

template <class T, class B = detail::empty_base<T>>
struct output_iteratable : incrementable<T, B> {};

template <class T, class P, class B = detail::empty_base<T>>
struct forward_iteratable : input_iteratable<T, P, B> {};

template <class T, class P, class B = detail::empty_base<T>>
struct bidirectional_iteratable : forward_iteratable<T, P, decrementable<T, B>> {};

/* To avoid repeated derivation from equality_comparable,
 * which is an indirect base class of bidirectional_iterable,
 * random_access_iteratable must not be derived from totally_ordered1
 * but from less_than_comparable1 only. */
template <class T, class P, class D, class R, class B = detail::empty_base<T> >
struct random_access_iteratable : bidirectional_iteratable<T, P, less_than_comparable1<T,
                                  additive2<T, D, indexable<T, D, R, B>>>> {};


namespace detail {
    struct true_t {};
    struct false_t {};
}

template<class T> struct is_chained_base {
    typedef detail::false_t value;
};

// Import a 4-type-argument operator template into boost (if necessary) and
// provide a specialization of 'is_chained_base<>' for it.
#define OPERATOR_TEMPLATE4(template_name4)                \
template<class T, class U, class V, class W, class B>     \
struct is_chained_base<template_name4<T, U, V, W, B> > {  \
    typedef detail::true_t value;                         \
};

#define OPERATOR_TEMPLATE3(template_name3)             \
template<class T, class U, class V, class B>           \
struct is_chained_base<template_name3<T, U, V, B> > {  \
    typedef detail::true_t value;                      \
};

#define OPERATOR_TEMPLATE2(template_name2)          \
template<class T, class U, class B>                 \
struct is_chained_base<template_name2<T, U, B> > {  \
    typedef detail::true_t value;                   \
};

#define OPERATOR_TEMPLATE1(template_name1)       \
template<class T, class B>                       \
struct is_chained_base<template_name1<T, B> > {  \
    typedef detail::true_t value;                \
};

#define OPERATOR_TEMPLATE(template_name)                  \
template <class T,                                        \
          class U = T                                     \
          class B = detail::empty_base<T>                 \
          class O = typename is_chained_base<U>::value    \
         >                                                \
struct template_name : template_name##2<T, U, B> {};      \
                                                          \
template<class T, class U, class B>                       \
struct template_name<T, U, B, detail::true_t>             \
  : template_name##1<T, U> {};                            \
                                                          \
template <class T, class B>                               \
struct template_name<T, T, B, detail::false_t>            \
  : template_name##1<T, B> {};                            \
                                                          \
template<class T, class U, class B, class O>              \
struct is_chained_base< template_name<T, U, B, O> > {     \
  typedef detail::true_t value;                           \
};                                                        \
                                                          \
OPERATOR_TEMPLATE2(template_name##2)                      \
OPERATOR_TEMPLATE1(template_name##1)

OPERATOR_TEMPLATE(less_than_comparable)
OPERATOR_TEMPLATE(equality_comparable)
OPERATOR_TEMPLATE(multipliable)
OPERATOR_TEMPLATE(addable)
OPERATOR_TEMPLATE(subtractable)
OPERATOR_TEMPLATE2(subtractable2_left)
OPERATOR_TEMPLATE(dividable)
OPERATOR_TEMPLATE2(dividable2_left)
OPERATOR_TEMPLATE(modable)
OPERATOR_TEMPLATE2(modable2_left)
OPERATOR_TEMPLATE(xorable)
OPERATOR_TEMPLATE(andable)
OPERATOR_TEMPLATE(orable)

OPERATOR_TEMPLATE1(incrementable)
OPERATOR_TEMPLATE1(decrementable)

OPERATOR_TEMPLATE2(dereferenceable)
OPERATOR_TEMPLATE3(indexable)

OPERATOR_TEMPLATE(left_shiftable)
OPERATOR_TEMPLATE(right_shiftable)
OPERATOR_TEMPLATE(equivalent)
OPERATOR_TEMPLATE(partially_ordered)

OPERATOR_TEMPLATE(totally_ordered)
OPERATOR_TEMPLATE(additive)
OPERATOR_TEMPLATE(multiplicative)
OPERATOR_TEMPLATE(integer_multiplicative)
OPERATOR_TEMPLATE(arithmetic)
OPERATOR_TEMPLATE(integer_arithmetic)
OPERATOR_TEMPLATE(bitwise)
OPERATOR_TEMPLATE1(unit_steppable)
OPERATOR_TEMPLATE(shiftable)
OPERATOR_TEMPLATE(ring_operators)
OPERATOR_TEMPLATE(ordered_ring_operators)
OPERATOR_TEMPLATE(field_operators)
OPERATOR_TEMPLATE(ordered_field_operators)
OPERATOR_TEMPLATE(euclidian_ring_operators)
OPERATOR_TEMPLATE(ordered_euclidian_ring_operators)
OPERATOR_TEMPLATE(euclidean_ring_operators)
OPERATOR_TEMPLATE(ordered_euclidean_ring_operators)
OPERATOR_TEMPLATE2(input_iteratable)
OPERATOR_TEMPLATE1(output_iteratable)
OPERATOR_TEMPLATE2(forward_iteratable)
OPERATOR_TEMPLATE2(bidirectional_iteratable)
OPERATOR_TEMPLATE4(random_access_iteratable)

#undef OPERATOR_TEMPLATE
#undef OPERATOR_TEMPLATE4
#undef OPERATOR_TEMPLATE3
#undef OPERATOR_TEMPLATE2
#undef OPERATOR_TEMPLATE1

}
